package com.example.springdatajpaderivedquerymethod.service.impl;

import com.example.springdatajpaderivedquerymethod.model.Employee;
import com.example.springdatajpaderivedquerymethod.repositories.EmployeeDAO;
import com.example.springdatajpaderivedquerymethod.service.EmployeeDAOService;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;

@Service
public class EmployeeDAOServiceImpl implements EmployeeDAOService {
    @Autowired
    private EmployeeDAO employeeDAO;

    @Override
    public List<Employee> findByCriteria(String employeeName) {
        List<Employee> employees = employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (employeeName != null) {
                    predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("name"),employeeName)));
                }
                Predicate predicate = criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
                return predicate;
            }
        });
        return employees;
    }

    @Override
    public List<Employee> findByCriteria(String deptName, Integer salary) {
        List<Employee> employees = employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (deptName != null) {
                    predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("dept"),deptName)));
                }
                if (salary != null) {
                    predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("salary"),salary));
                }
                Predicate predicate = criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
                return predicate;
            }
        });
        return employees;
    }

    @Override
    public List<Employee> findByDeptNameAndSalaryBetween(String deptName, Integer salaryMin, Integer salaryMax) {
        List<Employee> employees = employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (deptName != null) {
                    predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("dept"),deptName)));
                }
                if (salaryMin != null && salaryMax != null) {
                    predicates.add(criteriaBuilder.and(criteriaBuilder.between(root.get("salary"),salaryMin,salaryMax)));
                }else if(salaryMin != null && salaryMax == null){
                    predicates.add(criteriaBuilder.and(criteriaBuilder.greaterThanOrEqualTo(root.get("salary"),salaryMin)));
                }else if(salaryMin == null && salaryMax != null){
                    predicates.add(criteriaBuilder.and(criteriaBuilder.lessThanOrEqualTo(root.get("salary"),salaryMax)));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        });
        return employees;
    }

    @Override
    public List<Employee> findByPagingCriteria(String deptName, Pageable pageable) {
        Page<Employee> employeePage =  employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (deptName != null) {
                    predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("dept"),deptName)));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        },pageable);
        System.err.println("totalElements:" + employeePage.getTotalElements());
        System.err.println("totalPages:" + employeePage.getTotalPages());
        return employeePage.getContent();
    }

    @Override
    public List<Employee> findByCriteriaOrderBy(String deptName, String orderFieldName, String direction) {
        List<Employee> employees = employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (deptName != null) {
                    predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("dept"),deptName)));
                }
                if (direction.equals("asc")) {
                    query.orderBy(criteriaBuilder.asc(root.get("salary")));
                }else if(direction.equals("desc")){
                    query.orderBy(criteriaBuilder.desc(root.get("salary")));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        });
        return employees;
    }

    @Override
    public List<Employee> findByLikeAndBetween(String text, Integer salaryStart, Integer salaryEnd) {
        List<Employee> employees = employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (text != null) {
                    predicates.add(criteriaBuilder.or(
                            criteriaBuilder.like(root.get("dept"),"%" + text + "%"),
                            criteriaBuilder.like(root.get("name"),"%" + text + "%")
                    ));
                }
                if(salaryStart != null && salaryEnd != null){
                    predicates.add(criteriaBuilder.between(root.get("salary"),salaryStart,salaryEnd));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        });
        return employees;
    }

    @Override
    public List<Employee> findByInCriteria(List<String> names) {
        return employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (CollectionUtils.isNotEmpty(names)) {
                    predicates.add(root.get("name").in(names));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        });
    }

    @Override
    public List<Employee> findByNotInCriteria(List<String> names) {
        return employeeDAO.findAll(new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (CollectionUtils.isNotEmpty(names)) {
                    predicates.add(criteriaBuilder.not(root.get("name").in(names)));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        });
    }
}
